package com.love.netty.demo;

import com.love.netty.contants.NettyContants;
import com.love.netty.core.encode.Spliter;
import com.love.netty.demo.handler.*;
import com.love.netty.demo.handler.request.*;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

import static com.love.netty.contants.NettyContants.PORT;

/**
 * @ClassName: NettyServer
 * @Description:
 * @Author: wuj
 * @Date: 2021-06-22 17:36
 **/
@Slf4j
@Component
public class NettyServer implements ApplicationRunner, ApplicationListener<ContextClosedEvent>, ApplicationContextAware {


    private static final ServerBootstrap serverBootstrap = new ServerBootstrap();

    private static final AttributeKey<Object> KEY = AttributeKey.newInstance("serverName");

    private ApplicationContext applicationContext;

    private Channel serverChannel;

    @Autowired
    private HttpRequestHandler httpRequestHandler;

    @Autowired
    private TextWebSocketFrameHandler textWebSocketFrameHandler;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        //创建NioEventLoopGroup，这两个对象可以看做是传统IO编程模型的两大线程组
        //bossGroup表示监听端口，accept 新连接的线程组，
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        //workerGroup表示处理每一条连接的数据读写的线程组，
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        //这个类将引导我们进行服务端的启动工作
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap
                .group(bossGroup, workerGroup)
                //指定 IO 模型 NIO
                .channel(NioServerSocketChannel.class)
                //表示是否开启TCP底层心跳机制，true为开启
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                //指定一些自定义属性 其实说白了就是给NioServerSocketChannel维护一个map
                .attr(KEY, "nettyServer")
                //表示是否开启Nagle算法，true表示关闭，false表示开启，通俗地说，如果要求高实时性，
                //有数据发送时就马上发送，就关闭，如果需要减少发送次数减少网络交互，就开启
                .childOption(ChannelOption.TCP_NODELAY, true)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) {

                        // WebSocket 是基于 Http 协议的，要使用 Http 解编码器
                        ch.pipeline().addLast("http-codec", new HttpServerCodec());
                        // 用于大数据流的分区传输
                        ch.pipeline().addLast("http-chunked",new ChunkedWriteHandler());
                        // 将多个消息转换为单一的 request 或者 response 对象，最终得到的是 FullHttpRequest 对象
                        ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
                        // 创建 WebSocket 之前会有唯一一次 Http 请求 (Header 中包含 Upgrade 并且值为 websocket)
                        ch.pipeline().addLast("http-request",httpRequestHandler);
                        // 处理所有委托管理的 WebSocket 帧类型以及握手本身
                        // 入参是 ws://server:port/context_path 中的 contex_path
                        ch.pipeline().addLast("websocket-server", new WebSocketServerProtocolHandler("/ws"));
                        // WebSocket RFC 定义了 6 种帧，TextWebSocketFrame 是我们唯一真正需要处理的帧类型
                        ch.pipeline().addLast("text-frame",textWebSocketFrameHandler);





                       /* ch.pipeline().addLast(new IMIdleStateHandler());
                        ch.pipeline().addLast(new Spliter());
                        ch.pipeline().addLast(PacketCodecHandler.INSTANCE);
                        ch.pipeline().addLast(new HttpRequestHandler("/ws"));
                        //ch.pipeline().addLast(new TextWebSocketFrameHandler());
                        ch.pipeline().addLast(PacketCodecHandler.INSTANCE);
                        ch.pipeline().addLast(LoginRequestHandler.INSTANCE);
                        ch.pipeline().addLast(HeartBeatRequestHandler.INSTANCE);
                       // ch.pipeline().addLast(new AuthHandler());
                        ch.pipeline().addLast(IMHandler.INSTANCE);*/
                    }
                });
        Channel channel = bind(serverBootstrap, PORT);


        this.serverChannel = channel;
        channel.closeFuture().sync();
    }


    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        if (this.serverChannel != null) {
            this.serverChannel.close();
        }
        log.info("websocket 服务停止");
    }


    private static Channel bind(ServerBootstrap serverBootstrap, final int port) throws Exception {
        Channel channel = serverBootstrap.bind(port).addListener(new GenericFutureListener<Future<? super Void>>() {
            @Override
            public void operationComplete(Future<? super Void> future) throws Exception {
                if (future.isSuccess()) {
                    log.info("端口[" + port + "]绑定成功!");
                } else {
                    log.info("端口[" + port + "]绑定失败!");
                    bind(serverBootstrap, port + 1);
                }
            }
        }).sync().channel();
        return channel;


    }
}
